package sg.atom2d.tools.map;

import javax.swing.*;
import java.awt.Graphics.*;
import java.io.*;
import java.awt.*;
import java.util.*;
import java.net.*;

/**
 * Stores a set of tiles which can be requested by id number, or name
 *
 */
public class GraphicsBank {

    final static int DEFAULT_TILE_WIDTH = 32;
    final static int DEFAULT_TILE_HEIGHT = 32;
    final static String GB_VERSION = "1.0";
    final static String DELIM = ",";
    final static char COMMENT = '#';
    final static int ID = 0;
    final static int PATH = 1;
    final static int NAME = 2;
    final static int TYPE = 3;
    final static int EXTRA = 4;
    private ArrayList tiles;
    private ArrayList sprites;
    private ArrayList changeListeners;
    File loadedFrom;
    File baseDirectory;
    private boolean isUnsaved;
    Dimension baseTileSize;

    public GraphicsBank() {
        tiles = new ArrayList();
        sprites = new ArrayList();
        changeListeners = new ArrayList();
        loadedFrom = null;

        baseTileSize = new Dimension(DEFAULT_TILE_WIDTH, DEFAULT_TILE_HEIGHT);

        isUnsaved = true;
    }

    public void addSprite(String anmFile) {
        addSprite(new File(anmFile));
    }

    public void addSprite(File anmFile) {
    }

    public void loadTileset(String from) throws FileNotFoundException, IOException {
        loadTileset(new File(from));
    }

    /**
     * Load tileset from file. This will combine the tilesets if one has already
     * been loaded. The most recently loaded tileset file becomes the
     * "loadedFrom" tileset.
   *
     */
    public void loadTileset(File from) throws FileNotFoundException, IOException {
        BufferedReader r;
        String line;
        String tokens[];
        int id;
        int lineCount;
        File tileFile;


        if (tiles.size() > 0) {
            isUnsaved = true;
        } else {
            isUnsaved = false;
        }

        System.out.println(from);
        baseDirectory = from.getParentFile();
        this.loadedFrom = from;


        lineCount = 0;
        r = new BufferedReader(new FileReader(from));
        while (true) {
            line = r.readLine();
            lineCount++;

            //stop when no more lines
            if (line == null) {
                break;
            }
            line = line.trim();
            if (line.length() == 0 || line.charAt(0) == COMMENT) {
                continue; //skip comments and empty lines.
            }

            tokens = line.split(DELIM);
            if (tokens.length < 4) {
                System.err.println("Could not parse line " + lineCount + ". :");
                System.err.println(line);
                System.err.println("(There are not enough tokens)");
                continue;
            }
            try {
                id = Integer.parseInt(tokens[ID].trim());
            } catch (Exception e) {
                System.err.println("Could not parse line " + lineCount + ". :");
                System.err.println(line);
                System.err.println("(The tile id is not a valid number)");
                continue;
            }

            //get file for image
            tokens[PATH] = tokens[PATH].trim();
            tokens[NAME] = tokens[NAME].trim();
            tokens[TYPE] = tokens[TYPE].trim();

            tileFile = new File(baseDirectory, tokens[PATH]);
            //System.out.println("load tile image: "+tileFile);
            if (!tileFile.exists()) {
                tileFile = new File(tokens[PATH]);
                if (tileFile.exists()) {
                    System.err.println("WARNING: file " + tokens[PATH] + " not within the tilemaps working directory");
                } else {
                    r.close();
                    throw new FileNotFoundException("File " + tokens[PATH] + " referenced on line " + lineCount + " of " + from + " could not be found");
                }
            }

            //System.out.println("New tile: "+id+", name = "+tokens[NAME]);

            Tile t = null;

            if (tokens.length > EXTRA) {
                t = new Tile(id, tileFile.toString(), tokens[NAME].trim(), tokens[TYPE].trim(), tokens[EXTRA].trim());
            } else {
                t = new Tile(id, tileFile.toString(), tokens[NAME], tokens[TYPE]);
            }
            tiles.add(t);
        }
    } //end loadTileset

    /**
     * Save the tileset to the specified file. Will overwrite file if it exists.
   *
     */
    void saveTileset(File to) throws IOException {

        File base = to.getParentFile();
        PrintWriter w = new PrintWriter(new FileWriter(to));
        w.println("# Generated by version " + GB_VERSION + " of GraphicsBank");
        w.println("Tile Number, Image file, Tile Name, Type, Extended Info");
        System.out.println("Saving " + tiles.size() + " tiles.");
        Iterator i = tiles.iterator();
        while (i.hasNext()) {
            Tile t = (Tile) i.next();
            File tf = new File(t.getPath()).getCanonicalFile();
            String relPath = RelativePath.getRelativePath(new File(base.getCanonicalPath()), new File(tf.getCanonicalPath()));
            w.print("" + t.getNumber() + ", " + relPath + ", " + t.getName() + ", " + t.getType());
            if (t.getInfo() != null) {
                w.println(", " + t.getInfo());
            } else {
                w.println();
            }
        }
        w.close();
        baseDirectory = base;
        loadedFrom = to;
        isUnsaved = false;
    }

    /**
     * Get the tileset file this tileset was loaded from. May return NULL if
     * this is a new tileset that has never been saved.
   *
     */
    File getFile() {
        return loadedFrom;
    }

    /**
     * Get a tile by number
   *
     */
    Tile getTile(int number) {
        Iterator i = tiles.iterator();
        while (i.hasNext()) {
            Tile t = (Tile) i.next();
            if (t.number == number) {
                return t;
            }
        }
        return null;
    }

    /**
     * Get a tile by name. Case sensitive
   *
     */
    Tile getTile(String name) {
        Iterator i = tiles.iterator();
        while (i.hasNext()) {
            Tile t = (Tile) i.next();
            if (t.getName().equals(name)) {
                return t;
            }
        }
        return null;
    }

    /**
     * Rwemove a tile from the graphics bank. Any registered change listeners
     * will be notified.
   *
     */
    Tile remove(Tile t) {
        Tile rm = null;
        if (tiles.remove(t)) {
            rm = t;
        }
        if (t != null) {
            fireRemoveEvent(t);
            isUnsaved = true;
        }
        return t;
    }

    /**
     * Add a tile to the graphics bank. Any registered change listeners will be
     * notified.
   *
     */
    void add(Tile t) {
        /* TODO: validate the tile */
        tiles.add(t);
        isUnsaved = true;
        fireAddEvent(t);
    }

    int size() {
        return tiles.size();
    }

    Dimension getBaseTileSize() {
        return baseTileSize;
    }

    /**
     * Iterator for tile objects
   *
     */
    Iterator iterator() {
        return tiles.iterator();
    }

    /**
     * Set a global colour adjustment effect for all tiles in the graphics bank.
     * This will override any individually set effects on all tiles.
   *
     */
    public void setEffect(float r, float g, float b, float h, float s, float z) {
        Iterator i = tiles.iterator();
        while (i.hasNext()) {
            ((Tile) (i.next())).adjustRGBHS(r, g, b, h, s, z);
        }
    }

    /**
     * True if there have been any changes to the tileset since it was last
     * saved or loaded.
   *
     */
    public boolean isUnsaved() {
        if (getFile() == null) {
            return true;
        }
        return isUnsaved;
    }

    /**
     * Get the directory that the tileset file describing this tileset was found
     * in.
   *
     */
    File getBaseDirectory() {
        return baseDirectory;
    }

    /**
     * Get an unused tile number. Returns one more than the highest number used.
   *
     */
    int getUnusedNumber() {
        int n = 1;
        Iterator i = tiles.iterator();
        while (i.hasNext()) {
            Tile t = (Tile) i.next();
            if (n <= t.getNumber()) {
                n = t.getNumber() + 1;
            }
        }
        return n;
    }

    /**
     * Add a GraphicsBankChangeListener to the graphics bank. This listener will
     * be notified whenever the tileset is changed.
   *
     */
    void addChangeListener(GraphicsBankChangeListener l) {
        changeListeners.add(l);
    }

    /**
     * Remove a GraphicsBankChangeListener from the graphics bank.
   *
     */
    void removeChangeListener(GraphicsBankChangeListener l) {
        changeListeners.remove(l);
    }

    /**
     * May be called directly, and will cause the tilesetUpdated() method of
     * every registered GraphicsBankChangeListener object to be called.
   *
     */
    public void fireChangeEvent() {
        GraphicsBankChangeListener l;
        Iterator i = changeListeners.iterator();
        while (i.hasNext()) {
            l = (GraphicsBankChangeListener) i.next();
            l.tilesetUpdated(this);
        }
    }

    /**
     * Called whenever a tile is added using add(). will cause the tileAdded()
     * method of every registered GraphicsBankChangeListener object to be
     * called.
   *
     */
    private void fireAddEvent(Tile t) {
        System.out.println("Fire add event");
        GraphicsBankChangeListener l;
        Iterator i = changeListeners.iterator();
        while (i.hasNext()) {
            l = (GraphicsBankChangeListener) i.next();
            l.tileAdded(this, t);
        }
    }

    /**
     * Called whenever a tile is removed using remove(). will cause the
     * tileRemoved() method of every registered GraphicsBankChangeListener
     * object to be called.
   *
     */
    private void fireRemoveEvent(Tile t) {
        GraphicsBankChangeListener l;
        Iterator i = changeListeners.iterator();
        while (i.hasNext()) {
            l = (GraphicsBankChangeListener) i.next();
            l.tileRemoved(this, t);
        }
    }
}

interface GraphicsBankChangeListener {
    /* Large change happened such as loading a tileset */

    public void tilesetUpdated(GraphicsBank bank);
    /* A single tile was removed */

    public void tileRemoved(GraphicsBank bank, Tile removed);
    /* A single tile was added */

    public void tileAdded(GraphicsBank bank, Tile added);
}